#include <assert.h>
#include <math.h>
#include <stdio.h>

#include "../src/data.c"

static void
fill (double*           x,
      double          (*func) (void),
      unsigned int      n)
{
  for (unsigned int i = 0; i < n; ++i)
    {
      x[i] = func ();
    }
}

static void
fill_i (int*            x,
        int           (*func) (void),
        unsigned int    n)
{
  for (unsigned int i = 0; i < n; ++i)
    {
      x[i] = func ();
    }
}

static void
fill_r (Rating*         x,
        unsigned int    n_workers,
        unsigned int    n_labels,
        unsigned int    n_images,
        unsigned int    n)
{
  for (unsigned int i = 0; i < n; ++i)
    {
      x[i].worker = (lrand48 () >> 16) % n_workers;
      x[i].image = (lrand48 () >> 16) % n_images;
      x[i].rating = (lrand48 () >> 16) % n_labels;
    }
}

static double
one (void)
{
  return 1.0;
}

static int
minus_one (void)
{
  return -1.0;
}

static int
unsigned_int_cmp (int a,
                  int b)
{
  if (a < b) { return -1; }
  else if (a > b) { return 1; }
  else { return 0; }
}

static int
double_cmp (double a,
            double b)
{
  if (a < b) { return -1; }
  else if (a > b) { return 1; }
  else { return 0; }
}

static int
fuzzy_cmp (const double* a,
           const double* b,
           unsigned int  n)
{
  const double epsilon = 1e-5;

  for (unsigned int i = 0; i < n; ++i)
    {
      int rv = fabs (a[i] - b[i]) < epsilon ? 0 : double_cmp (a[i], b[i]);

      if (rv) { return rv; }
    }

  return 0;
}

static int
data_cmp (const Data* a,
          const Data* b)
{
  return double_cmp (a->kappa, b->kappa) ||
         memcmp (a->logalpha,
                 b->logalpha,
                 a->n_images * sizeof (*a->logalpha)) ||
         unsigned_int_cmp (a->n_workers, b->n_workers) ||
         unsigned_int_cmp (a->n_labels, b->n_labels) ||
         memcmp (a->tau,
                 b->tau,
                 a->n_workers * (a->n_labels - 1) * sizeof (*a->tau)) ||
         unsigned_int_cmp (a->n_images, b->n_images) ||
         memcmp (a->gamma, b->gamma, (a->n_labels - 1) * sizeof (*a->gamma)) ||
         double_cmp (a->rho, b->rho) ||
         memcmp (a->loglambda,
                 b->loglambda,
                 a->n_workers * sizeof (*a->loglambda)) ||
         fuzzy_cmp (a->priorz, b->priorz, a->n_labels) ||
         memcmp (a->clamp, b->clamp, a->n_images * sizeof (*a->clamp)) || 
         memcmp (a->ratings, b->ratings, a->n_ratings * sizeof (*a->ratings)) ||
         unsigned_int_cmp (a->n_ratings, b->n_ratings) ||
         double_cmp (a->priorkappa.mean, b->priorkappa.mean) ||
         double_cmp (a->priorkappa.sq_stddev, b->priorkappa.sq_stddev) ||
         double_cmp (a->priorlogalpha.sq_stddev, b->priorlogalpha.sq_stddev) ||
         double_cmp (a->priortau.sq_stddev, b->priortau.sq_stddev) ||
         memcmp (a->normalpriorgamma,
                 b->normalpriorgamma,
                 (a->n_labels - 1) * sizeof (*a->normalpriorgamma)) ||
         double_cmp (a->priorrho.mean, b->priorrho.mean) ||
         double_cmp (a->priorrho.sq_stddev, b->priorrho.sq_stddev) ||
         double_cmp (a->priorloglambda.sq_stddev, b->priorloglambda.sq_stddev);
}

static void
test_data_inverse_once (void)
{
  /* read_clamp (read_data (print_data (data), print_clamp (data))) == data */

  Data data;
  Data data_dup;

  data.n_workers = 1 + (lrand48 () >> 16) % 10;
  data.n_labels = 1 + (lrand48 () >> 16) % 10;
  data.n_images = 1 + (lrand48 () >> 16) % 10;
  data.n_ratings = 1 + (lrand48 () >> 16) % 10;

  data.kappa = 1;
  data.logalpha = malloc (data.n_images * sizeof (*data.logalpha));
  fill (data.logalpha, one, data.n_images);
  data.tau = malloc (data.n_workers * (data.n_labels - 1) * sizeof (*data.tau));
  for (unsigned int i = 0; i < data.n_workers; ++i)
    {
      for (unsigned int j = 0; j + 1 < data.n_labels; ++j)
        {
          data.tau[i * (data.n_labels - 1) + j] = j + 0.5;
        }
    }
  data.gamma = malloc ((data.n_labels - 1) * sizeof (*data.gamma));
  for (unsigned int i = 0; i + 1 < data.n_labels; ++i)
    {
      data.gamma[i] = i + 0.5;
    }
  data.rho = 0;
  data.loglambda = malloc (data.n_workers * sizeof (*data.loglambda));
  for (unsigned int i = 0; i < data.n_workers; ++i)
    {
      data.loglambda[i] = 0;
    }
  data.pz = malloc (data.n_images * data.n_labels * sizeof (*data.pz));
  fill (data.pz, drand48, data.n_images * data.n_labels);
  data.priorz = malloc (data.n_labels * sizeof (*data.priorz));
  fill (data.priorz, drand48, data.n_labels);
  double sum = data.priorz[0];
  for (unsigned int i = 1; i < data.n_labels; ++i)
    {
      sum += data.priorz[i];
    }
  for (unsigned int i = 0; i < data.n_labels; ++i)
    {
      data.priorz[i] /= sum;
    }
  data.clamp = malloc (data.n_images * sizeof (*data.priorz));
  fill_i (data.clamp, minus_one, data.n_images);
  for (unsigned int i = 0; i < data.n_images; ++i)
    {
      if (drand48 () < 0.5) 
        { 
          data.clamp[i] = (int) (drand48 () * data.n_labels); 
        }
    }
  data.ratings = malloc (data.n_ratings * sizeof (*data.ratings));
  fill_r (data.ratings,
          data.n_workers,
          data.n_labels,
          data.n_images,
          data.n_ratings);
  data.priorkappa = normal_distribution (1, 1);
  data.priorlogalpha = parameterized_normal_distribution (1);
  data.priortau = parameterized_normal_distribution (1);

  data.normalpriorgamma = malloc ((data.n_labels - 1) * sizeof (*data.normalpriorgamma));
  data.priorgamma = malloc ((data.n_labels - 1) * sizeof (*data.priorgamma));
  for (unsigned int i = 0; i + 1 < data.n_labels; ++i)
    {
      data.normalpriorgamma[i] = normal_distribution (i + 0.5, 1);
      data.priorgamma[i] = &data.normalpriorgamma[i].base;
    }

  data.priorrho = normal_distribution (0, 1);
  data.priorloglambda = parameterized_normal_distribution (1);

  FILE* fp = tmpfile ();

  print_data (fp, &data);
  print_clamp (fp, &data);
  fflush (fp);
  rewind (fp);
  read_data (fp, &data_dup);
  read_clamp (fp, &data_dup);

  assert (data_cmp (&data, &data_dup) == 0);

  fclose (fp);
  data_destruct (&data_dup);
  data_destruct (&data);
}

static void
test_data_inverse (void)
{
  for (unsigned int i = 0; i < 100; ++i)
    {
      test_data_inverse_once ();
    }
}

int
main (void)
{
  test_data_inverse ();

  return 0;
}
